/****************************************************************************
 * Copyright 2010 Huascar A. Sanchez                                        *
 *                                                                          *
 * Licensed under the Apache License, Version 2.0 (the "License");          *
 * you may not use this file except in compliance with the License.         *
 * You may obtain a copy of the License at                                  *
 *                                                                          *
 *     http://www.apache.org/licenses/LICENSE-2.0                           *
 *                                                                          *
 * Unless required by applicable law or agreed to in writing, software      *
 * distributed under the License is distributed on an "AS IS" BASIS,        *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
 * See the License for the specific language governing permissions and      *
 * limitations under the License.                                           *
 ****************************************************************************/
package com.gotobject.greatq.datastore;

import com.google.apphosting.api.ApiProxy;
import com.gotobject.filter.internal.ToStrings;
import org.jiql.db.GroupBy;
import org.jiql.util.SQLParser;

import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;

import static com.gotobject.filter.internal.Pair.of;
import static com.gotobject.filter.internal.ToStrings.str;

/**
 * @author huascar.sanchez@gmail.com (Huascar A. Sanchez)
 */
public class DatastoreSQLParser extends SQLParser {
    private final AtomicReference<GroupBy> atomicGroupBy = new AtomicReference<GroupBy>();
    private       SQLParser owner;

    public DatastoreSQLParser(SQLParser parser) throws SQLException {
        this(parser.getToken(), new DatastoreConnection(parser.getProperties()));
        owner =  parser;
        updateInternals();
    }

    public DatastoreSQLParser(String s, DatastoreConnection datastoreConnection) throws SQLException {
        super(s, datastoreConnection);
        owner = null;
    }

    public DatastoreSQLParser(Properties properties) throws SQLException {
        super(properties);
        owner = null;
    }


    void updateInternals() throws SQLException {
        if(owner != null){
            setAction(owner.getAction());
            final Hashtable all = getField(owner.getClass(), "attributes", false);
            for(Object each : all.keySet()){
                setAttribute(str(each), all.get(each));
            }
            final ApiProxy.Environment e = new ApiProxy.Environment(){
                @Override
                public String getAppId() {
                    return "https://jiqladmin.appspot.com/jiqlservlet";
                }

                @Override
                public String getVersionId() {
                    return "1.0";  //todo
                }

                @Override
                public String getEmail() {
                    return null;  //todo
                }

                @Override
                public boolean isLoggedIn() {
                    return false;  //todo
                }

                @Override
                public boolean isAdmin() {
                    return false;  //todo
                }

                @Override
                public String getAuthDomain() {
                    return null;  //todo
                }

                @Override
                public String getRequestNamespace() {
                    return "";  //todo
                }

                @Override
                public Map<String, Object> getAttributes() {
                    return new HashMap<String, Object>();
                }
            };
            //ApiProxy.setEnvironmentForCurrentThread(e);

            setOriginalSelectList(owner.getOriginalSelectList());
            setResults(owner.getResults());
            setResultsTable(owner.getResultsTable());
            setSpecial(owner.isSpecial());
            //setSQLParser(owner.getStatement(), getProperties());  // here is where it blows
            setStatementProcessor(owner.getStatementProcessor());
            setField(getClass().getSuperclass(), "properties", this.<Object>getField(owner.getClass(), "properties", false));
            setField(getClass().getSuperclass(),"table", owner.getTable());
            setField(getClass().getSuperclass(), "toko", owner.getStatement());
            setField(getClass().getSuperclass(), "loadtable", this.<Object>getField(owner.getClass(), "loadtable", false));
            setField(getClass().getSuperclass(), "inserttable", this.<Object>getField(owner.getClass(), "inserttable", false));
            setField(getClass().getSuperclass(), "selectP", this.<Object>getField(owner.getClass(), "selectP", false));
            setField(getClass().getSuperclass(), "createP", this.<Object>getField(owner.getClass(), "createP", false));
            setField(getClass().getSuperclass(), "insertP", this.<Object>getField(owner.getClass(), "insertP", false));            
            setTableInfo(owner.getTableInfo());
            setField(getClass().getSuperclass(), "ti", this.<Object>getField(owner.getClass(), "ti", false));
        }
    }


    private void setField(Class<?> klass, String name, Object value) throws SQLException {
        try {
            for(Field each : klass.getDeclaredFields()){
                if(each.getName().equalsIgnoreCase(name)){
                    each.setAccessible(true);
                    each.set(this, value);
                }
            }
        } catch (Exception e){
            throw new SQLException(e);
        }
    }

    private <T> T getField(Class<?> klass, String name, boolean isStatic) throws SQLException {
        try {
            for(Field each : klass.getDeclaredFields()){
                if(each.getName().equalsIgnoreCase(name)){
                    each.setAccessible(true);
                    //noinspection unchecked
                    return (T) each.get(isStatic ? null : owner);
                }
            }

            throw new NoSuchFieldException("error: element not found");
        } catch (Exception e) {
            throw new SQLException(e);
        }
    }


    public GroupBy getGroupBy() {
        try {
            final GroupBy current =  peek(super.getClass().getSuperclass(), "groupby");
            if(atomicGroupBy.compareAndSet(null, current)){
                return atomicGroupBy.get();
            } else {
                if(atomicGroupBy.compareAndSet(atomicGroupBy.get(), current)){
                    return atomicGroupBy.get();
                } else {
                    return atomicGroupBy.get();
                }
            }
        } catch (Exception e) {
            return null;
        }

    }

    private GroupBy peek(Class<?> klass, String name) throws SQLException {
        try {
            for(Field each : klass.getDeclaredFields()){
                if(each.getName().equalsIgnoreCase(name)){
                    each.setAccessible(true);
                    return (GroupBy) each.get(this);
                }
            }

            throw new NoSuchFieldException("error: element not found");
        } catch (Exception e) {
            throw new SQLException(e);
        }
    }




    @Override
    public String toString() {
        return ToStrings.toString(
                this,
                of("table", getTable()),
                of("aliases", str(getAliases())),
                of("values", str(getHash())),
                of("selects", str(getSelectList())),
                of("selectAS", str(getSelectAS())),
                of("selectAS2", str(getIncludeAllList())),
                of("eitheroralllist", str(getEitherOrAllList())),
                of("originalSelectList", str(getOriginalSelectList())),
                of("groupby", str(getGroupBy())),
                of("union", str(getUnion()))
        );
    }
}
